/*
 * Copyright 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.jumpnote.android;

import android.app.Activity;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.EditText;
import android.widget.Toast;

/**
 * A generic activity for editing a note in a database. This can be used either
 * to simply view a note {@link Intent#ACTION_VIEW}, view and edit a note
 * {@link Intent#ACTION_EDIT}, or create a new note {@link Intent#ACTION_INSERT}
 * .
 */
public class NoteEditor extends Activity implements TextWatcher {
    static final String TAG = Config.makeLogTag(NoteEditor.class);

    /**
     * Standard projection for the interesting columns of a normal note.
     */
    private static final String[] PROJECTION = new String[] {
        JumpNoteContract.Notes._ID, // 0
        JumpNoteContract.Notes.BODY, // 1
    };

    private static final int COLUMN_INDEX_NOTE = 1;

    // This is our state data that is stored when freezing.
    private static final String ORIGINAL_CONTENT = "origContent";

    // Identifiers for our menu items.
    private static final int MENU_REVERT = Menu.FIRST;
    private static final int MENU_DISCARD = Menu.FIRST + 1;
    private static final int MENU_DELETE = Menu.FIRST + 2;

    // The different distinct states the activity can be run in.
    private static final int STATE_EDIT = 0;
    private static final int STATE_INSERT = 1;

    private int mState;
    private boolean mDirty = false;
    private Uri mUri;
    private Cursor mCursor;
    private EditText mText;
    private String mOriginalContent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final Intent intent = getIntent();

        // Do some setup based on the action being performed.

        final String action = intent.getAction();
        if (Intent.ACTION_EDIT.equals(action)) {
            // Requested to edit: set that state, and the data being edited.
            mState = STATE_EDIT;
            mUri = intent.getData();
        } else if (Intent.ACTION_INSERT.equals(action)) {
            // Requested to insert: set that state, and create a new entry
            // in the container.
            mState = STATE_INSERT;
            mDirty = true;
            mUri = getContentResolver().insert(intent.getData(), null);

            // If we were unable to create a new note, then just finish
            // this activity. A RESULT_CANCELED will be sent back to the
            // original activity if they requested a result.
            if (mUri == null) {
                Log.e(TAG, "Failed to insert new note into " + getIntent().getData());
                finish();
                return;
            }

            // The new entry was created, so assume all will end well and
            // set the result to be returned.
            setResult(RESULT_OK, (new Intent()).setAction(mUri.toString()));

        } else {
            // Whoops, unknown action! Bail.
            Log.e(TAG, "Unknown action, exiting");
            finish();
            return;
        }

        // Set the layout for this activity. You can find it in
        // res/layout/note_editor.xml
        setContentView(R.layout.note_editor);

        // The text view for our note, identified by its ID in the XML file.
        mText = (EditText) findViewById(R.id.note);

        // Get the note!
        mCursor = managedQuery(mUri, PROJECTION, null, null, null);
        if (mCursor == null) {
            Log.e(TAG, "Couldn't perform notes query on " + mUri);
            Toast.makeText(this, "Couldn't perform notes query", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        // If an instance of this activity had previously stopped, we can
        // get the original text it started with.
        if (savedInstanceState != null) {
            mOriginalContent = savedInstanceState.getString(ORIGINAL_CONTENT);
        }

        mText.addTextChangedListener(this);
    }

    @Override
    protected void onResume() {
        super.onResume();

        // Make sure we are at the one and only row in the cursor.
        mCursor.moveToFirst();

        // Modify our overall title depending on the mode we are running in.
        if (mState == STATE_EDIT) {
            setTitle(getText(R.string.title_edit));
        } else if (mState == STATE_INSERT) {
            setTitle(getText(R.string.title_create));
        }

        // This is a little tricky: we may be resumed after previously being
        // paused/stopped. We want to put the new text in the text view,
        // but leave the user where they were (retain the cursor position
        // etc). This version of setText does that for us.
        String note = mCursor.getString(COLUMN_INDEX_NOTE);
        mText.setTextKeepState(note);
        mDirty = false;

        // If we hadn't previously retrieved the original text, do so
        // now. This allows the user to revert their changes.
        if (mOriginalContent == null) {
            mOriginalContent = note;
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        // Save away the original text, so we still have it if the activity
        // needs to be killed while paused.
        outState.putString(ORIGINAL_CONTENT, mOriginalContent);
    }

    @Override
    protected void onPause() {
        super.onPause();

        String text = mText.getText().toString();
        int length = text.length();

        // If this activity is finished, and there is no text, then we
        // do something a little special: simply delete the note entry.
        // Note that we do this both for editing and inserting... it
        // would be reasonable to only do it when inserting.
        if (isFinishing() && (length == 0)) {
            setResult(RESULT_CANCELED);
            deleteNote();

            // Get out updates into the provider.
        } else {
            ContentValues values = new ContentValues();

            // Bump the modification time to now.
            if (mDirty)
                values.put(JumpNoteContract.Notes.MODIFIED_DATE, System.currentTimeMillis());

            // If we are creating a new note, then we want to also create
            // an initial title for it.
            if (mState == STATE_INSERT) {
                String title = text.substring(0, Math.min(30, length));
                if (length > 30) {
                    int lastSpace = title.lastIndexOf(' ');
                    if (lastSpace > 0) {
                        title = title.substring(0, lastSpace);
                    }
                }
                values.put(JumpNoteContract.Notes.TITLE, title);
            }

            // Write our text back into the provider.
            values.put(JumpNoteContract.Notes.BODY, text);

            // Commit all of our changes to persistent storage. When the
            // update completes
            // the content provider will notify the cursor of the change,
            // which will
            // cause the UI to be updated.
            getContentResolver().update(mUri, values, null, null);

            if (mDirty)
                Toast.makeText(this, R.string.note_saved_message, Toast.LENGTH_SHORT).show();
            mDirty = false;
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);

        // Build the menus that are shown when editing.
        if (mState == STATE_EDIT) {
            menu.add(0, MENU_REVERT, 0, R.string.menu_revert).setShortcut('0', 'r').setIcon(
                    R.drawable.ic_menu_revert);
            menu.add(0, MENU_DELETE, 0, R.string.menu_delete).setShortcut('1', 'd').setIcon(
                    R.drawable.ic_menu_delete);

            // Build the menus that are shown when inserting.
        } else {
            menu.add(0, MENU_DISCARD, 0, R.string.menu_discard).setShortcut('0', 'd').setIcon(
                    R.drawable.ic_menu_delete);
        }

        // If we are working on a full note, then append to the
        // menu items for any other activities that can do stuff with it
        // as well. This does a query on the system for any activities that
        // implement the ALTERNATIVE_ACTION for our data, adding a menu item
        // for each one that is found.
        Intent intent = new Intent(null, getIntent().getData());
        intent.addCategory(Intent.CATEGORY_ALTERNATIVE);

        Intent specificEditTitle = new Intent(TitleEditor.EDIT_TITLE_ACTION, getIntent().getData());
        MenuItem[] specificMenuItems = new MenuItem[1];

        menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0, new ComponentName(this,
                NoteEditor.class), new Intent[]{ specificEditTitle }, intent, 0, specificMenuItems);

        // Customize the icon.
        if (specificMenuItems[0] != null) {
            specificMenuItems[0].setIcon(android.R.drawable.ic_menu_edit);
        }

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle all of the possible menu actions.
        switch (item.getItemId()) {
            case MENU_DELETE:
                deleteNote();
                finish();
                break;
            case MENU_DISCARD:
                cancelNote();
                break;
            case MENU_REVERT:
                cancelNote();
                break;
        }
        return super.onOptionsItemSelected(item);
    }

    /**
     * Take care of canceling work on a note. Deletes the note if we had created
     * it, otherwise reverts to the original text.
     */
    private final void cancelNote() {
        if (mState == STATE_EDIT) {
            // Put the original note text back into the database
            if (mCursor != null) {
                mCursor.close();
                mCursor = null;
            }
            ContentValues values = new ContentValues();
            // Bump the modification time to now.
            if (mDirty)
                values.put(JumpNoteContract.Notes.MODIFIED_DATE, System.currentTimeMillis());
            values.put(JumpNoteContract.Notes.BODY, mOriginalContent);
            getContentResolver().update(mUri, values, null, null);
        } else if (mState == STATE_INSERT) {
            // We inserted an empty note, make sure to delete it
            deleteNote();
        }
        setResult(RESULT_CANCELED);
        finish();
    }

    /**
     * Take care of deleting a note. Mark the note for deletion. The activity should be finished
     * after this call is made.
     */
    private final void deleteNote() {
        if (mCursor != null) {
            mCursor.close();
            mCursor = null;
        }
        mText.setText("");

        ContentValues values = new ContentValues();
        values.put(JumpNoteContract.Notes.MODIFIED_DATE, System.currentTimeMillis());
        values.put(JumpNoteContract.Notes.PENDING_DELETE, true);
        getContentResolver().update(mUri, values, null, null);
    }

    public void onTextChanged(CharSequence s, int start, int before, int count) {
    }

    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        mDirty = true;
    }

    public void afterTextChanged(Editable s) {
    }
}
